#include "lightmanager.h"

#include "dma.h"

#include <algorithm>

LightManager::LightManager(void) {
    this->ambientLight = Vector4(255.0f, 255.0f, 255.0f, 0.0f);
    this->lights = new vector<Light*>();
}

LightManager::~LightManager(void) {
    delete this->lights;
}

Vector4 LightManager::GetAmbientLight() const {
    return this->ambientLight;
}

void LightManager::SetAmbientLight(Vector4 val) {
    this->ambientLight = val;
}

void LightManager::Add(Light* light) {
    for (vector<Light*>::const_iterator it = this->lights->begin(); it != this->lights->end(); ++it) {
        if (*it == light) {
            return;
        }
    }

    this->lights->push_back(light);
}

void LightManager::Remove(Light* light) {
    vector<Light*>::iterator it = this->lights->begin();
    while (it != this->lights->end()) {
        if (light == (*it)) {
            this->lights->erase(it);
            return;
        } else {
            ++it;
        }
    }
}

void LightManager::UploadLights(Vector4 position) {
    vector<Light*> directionalLights;
    vector<PointLightDistance*> pointLights;
    for (vector<Light*>::reverse_iterator it = this->lights->rbegin(); it != this->lights->rend(); ++it) {
        Light* light = (*it);
        if (light->enabled) {
            if (light->type == LIGHTTYPE_DIRECTIONAL && directionalLights.size() < 3) {
                directionalLights.push_back(light);
            } else if (light->type == LIGHTTYPE_POINT) {
                float distance = (light->position - position).Length();
                PointLightDistance* pld = new PointLightDistance();
                pld->light = light;
                pld->distance = distance;
                pointLights.push_back(pld);
            }
        }
    }

    VIFDynamicDMA.Add32(FLUSH);
    VIFDynamicDMA.AddUnpack(V4_32, 1, 28);
    this->UploadDirectionalLights(&directionalLights);
    sort(pointLights.begin(), pointLights.end(), PointLightDistance::Compare);
    this->UploadPointLights(&pointLights);
    for (vector<PointLightDistance*>::iterator it = pointLights.begin(); it != pointLights.end(); ++it) {
        delete *it;
    }
}

void LightManager::UploadDirectionalLights(vector<Light*>* directionalLights) {
    Vector4 defaultDirection = Vector4(1.0f, 0.0f, 0.0f, 0.0f);
    Vector4 defaultColor = Vector4(0.0f, 0.0f, 0.0f, 0.0f);

    int d = 0;
    Matrix4x4 directions = Matrix4x4::IDENTITY;
    for (vector<Light*>::const_iterator it = directionalLights->begin(); it != directionalLights->end(); ++it) {
        directions(0, d) = (*it)->direction.x;
        directions(1, d) = (*it)->direction.y;
        directions(2, d) = (*it)->direction.z;
        d++;
    }

    for (d; d < 3; ++d) {
        directions(0, d) = defaultDirection.x;
        directions(1, d) = defaultDirection.y;
        directions(2, d) = defaultDirection.z;
    }

    VIFDynamicDMA.AddMatrix(directions);
    d = 0;
    for (vector<Light*>::const_iterator it = directionalLights->begin(); it != directionalLights->end(); ++it) {
        VIFDynamicDMA.AddVector((*it)->color);
        d++;
    }

    for (d; d < 3; ++d) {
        VIFDynamicDMA.AddVector(defaultColor);
    }

    VIFDynamicDMA.AddVector(this->ambientLight);
}

void LightManager::UploadPointLights(vector<PointLightDistance*>* pointLights) {
    Matrix4x4 positions = Matrix4x4::NULLMATRIX;
    Matrix4x4 colors = Matrix4x4::NULLMATRIX;
    Matrix4x4 attenuation = Matrix4x4::NULLMATRIX;

    int i = 0;
    for (vector<PointLightDistance*>::const_iterator it = pointLights->begin(); it != pointLights->end() && i < 4; ++it) {
        Light* light = (*it)->light;
        positions(i, 0) = light->position.x;
        positions(i, 1) = light->position.y;
        positions(i, 2) = light->position.z;
        positions(i, 3) = 1.0f;
        colors(i, 0) = light->color.x;
        colors(i, 1) = light->color.y;
        colors(i, 2) = light->color.z;
        colors(i, 3) = light->color.w;
        attenuation(i, 0) = light->attenuation0;
        attenuation(i, 1) = light->attenuation1;
        attenuation(i, 2) = light->attenuation2;
        attenuation(i, 3) = 0.0f;
        i++;
    }

    VIFDynamicDMA.AddMatrix(positions);
    VIFDynamicDMA.AddMatrix(colors);
    VIFDynamicDMA.AddMatrix(attenuation);
}
